---
title: '子組件'
---

:::caution

檢查和操作 `Children` 往往會導致應用程式中令人驚訝且難以解釋的行為。這可能導致邊緣情況，並且通常不會產生預期的結果。如果您嘗試操作 `Children`，則應考慮其他方法。

Yew 支援將 `Html` 用作子元件屬性的類型。如果您不需要 `Children` 或 `ChildrenRenderer`，則應使用 `Html` 作為子元件。它沒有 `Children` 的缺點，且效能開銷較低。

:::

## 一般用法

*大多數情況下，*當允許元件具有子元件時，您不關心元件具有的子元件的類型。在這種情況下，下面的範例就足夠了。

```rust
use yew::{html, Component, Context, Html, Properties};

#[derive(Properties, PartialEq)]
pub struct ListProps {
    #[prop_or_default]
    pub children: Html,
}

pub struct List;

impl Component for List {
    type Message = ();
    type Properties = ListProps;

    fn create(_ctx: &Context<Self>) -> Self {
        Self
    }

    fn view(&self, ctx: &Context<Self>) -> Html {
        html! {
            <div class="list">
                {ctx.props().children.clone()}
            </div>
        }
    }
}
```

## 進階用法

### 類型化子元件

在您希望將一種類型的元件作為子元件傳遞給您的元件的情況下，您可以使用 `yew::html::ChildrenWithProps<T>`。

```rust
use yew::{html, ChildrenWithProps, Component, Context, Html, Properties};

pub struct Item;

impl Component for Item {
    type Message = ();
    type Properties = ();

    fn create(_ctx: &Context<Self>) -> Self {
        Self
    }

    fn view(&self, _ctx: &Context<Self>) -> Html {
        html! {
            { "item" }
        }
    }
}

#[derive(Properties, PartialEq)]
pub struct ListProps {
    #[prop_or_default]
    pub children: ChildrenWithProps<Item>,
}

pub struct List;

impl Component for List {
    type Message = ();
    type Properties = ListProps;

    fn create(_ctx: &Context<Self>) -> Self {
        Self
    }

    fn view(&self, ctx: &Context<Self>) -> Html {
        html! {
            <div class="list">
                { for ctx.props().children.iter() }
            </div>
        }
    }
}
```

## 帶有屬性的巢狀子元件

如果包含元件對其子元件進行了類型化，則可以存取和變更巢狀元件的屬性。

```rust
use std::rc::Rc;
use yew::prelude::*;

#[derive(Clone, PartialEq, Properties)]
pub struct ListItemProps {
    value: String,
}

#[function_component]
fn ListItem(props: &ListItemProps) -> Html {
    let ListItemProps { value } = props.clone();
    html! {
        <span>
            {value}
        </span>
    }
}

#[derive(PartialEq, Properties)]
pub struct Props {
    pub children: ChildrenWithProps<ListItem>,
}

#[function_component]
fn List(props: &Props) -> Html {
    let modified_children = props.children.iter().map(|mut item| {
            let mut props = Rc::make_mut(&mut item.props);
            props.value = format!("item-{}", props.value);
            item
    });
    html! { for modified_children }
}

html! {
    <List>
        <ListItem value="a" />
        <ListItem value="b" />
        <ListItem value="c" />
    </List>
};
```

### 枚舉類型的子元件

當然，有時您可能需要將子元件限制為幾種不同的元件。在這些情況下，您必須更深入地了解 Yew。

這裡使用 [`derive_more`](https://github.com/JelteF/derive_more) 來提供更好的人體工學。如果您不想使用它，您可以為每個變體手動實現 `From`。

```rust
use yew::{
    html, html::ChildrenRenderer, virtual_dom::VChild, Component,
    Context, Html, Properties,
};

pub struct Primary;

impl Component for Primary {
    type Message = ();
    type Properties = ();

    fn create(_ctx: &Context<Self>) -> Self {
        Self
    }

    fn view(&self, _ctx: &Context<Self>) -> Html {
        html! {
            { "Primary" }
        }
    }
}

pub struct Secondary;

impl Component for Secondary {
    type Message = ();
    type Properties = ();

    fn create(_ctx: &Context<Self>) -> Self {
        Self
    }

    fn view(&self, _ctx: &Context<Self>) -> Html {
        html! {
            { "Secondary" }
        }
    }
}

#[derive(Clone, derive_more::From, PartialEq)]
pub enum Item {
    Primary(VChild<Primary>),
    Secondary(VChild<Secondary>),
}

// 現在，我們實現 `Into<Html>`，以便 yew 知道如何渲染 `Item`。
#[allow(clippy::from_over_into)]
impl Into<Html> for Item {
    fn into(self) -> Html {
        match self {
            Self::Primary(child) => child.into(),
            Self::Secondary(child) => child.into(),
        }
    }
}

#[derive(Properties, PartialEq)]
pub struct ListProps {
    #[prop_or_default]
    pub children: ChildrenRenderer<Item>,
}

pub struct List;

impl Component for List {
    type Message = ();
    type Properties = ListProps;

    fn create(_ctx: &Context<Self>) -> Self {
        Self
    }

    fn view(&self, ctx: &Context<Self>) -> Html {
        html! {
            <div class="list">
                { for ctx.props().children.iter() }
            </div>
        }
    }
}
```

### 可選類型的子元件

您也可以具有特定類型的單一可選子元件：

```rust
use yew::{
    html, html_nested, virtual_dom::VChild, Component,
    Context, Html, Properties
};

pub struct PageSideBar;

impl Component for PageSideBar {
    type Message = ();
    type Properties = ();

    fn create(_ctx: &Context<Self>) -> Self {
        Self
    }

    fn view(&self, _ctx: &Context<Self>) -> Html {
        html! {
            { "sidebar" }
        }
    }
}

#[derive(Properties, PartialEq)]
pub struct PageProps {
    #[prop_or_default]
    pub sidebar: Option<VChild<PageSideBar>>,
}

struct Page;

impl Component for Page {
    type Message = ();
    type Properties = PageProps;

    fn create(_ctx: &Context<Self>) -> Self {
        Self
    }

    fn view(&self, ctx: &Context<Self>) -> Html {
        html! {
            <div class="page">
                { ctx.props().sidebar.clone().map(Html::from).unwrap_or_default() }
                // ... 页面内容
            </div>
        }
    }
}

// 页面组件可以选择是否附带侧边栏：

pub fn render_page(with_sidebar: bool) -> Html {
    if with_sidebar {
        // 附带侧边栏的页面
        html! {
            <Page sidebar={html_nested! {
                <PageSideBar />
            }} />
        }
    } else {
        // 不附带侧边栏的页面
        html! {
            <Page />
        }
    }
}
```

## 進一步閱讀

- 有關此模式的真實範例，請查閱 yew-router 的原始程式碼。有關更高級的範例，請查看 yew 儲存庫中的[相關範例清單](https://github.com/yewstack/yew/tree/master/examples/nested_list)
